package com.github.jing332.tts.speech.local

import android.content.Context
import android.os.Bundle
import android.speech.tts.TextToSpeech
import android.speech.tts.UtteranceProgressListener
import android.speech.tts.Voice
import com.github.jing332.database.entities.systts.AudioParams
import com.github.jing332.database.entities.systts.source.LocalTtsParameter
import com.github.jing332.database.entities.systts.source.LocalTtsSource
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.io.File
import java.io.InputStream
import java.io.PipedInputStream
import java.io.PipedOutputStream
import java.util.*
import kotlin.collections.forEach
import kotlin.coroutines.resume


/**
 * `AndroidTtsEngine` is a wrapper class for the Android Text-to-Speech (TTS) engine.
 * It provides a simplified interface for initializing, managing, and utilizing the TTS engine
 * for speech synthesis, including generating audio streams and directly playing speech.
 *
 * @property context The application context, used for accessing system resources and initializing the TTS engine.
 * @property cacheDir The directory where temporary audio files generated by the TTS engine will be stored.
 *                   Defaults to the external cache directory under "AndroidTTS".
 */
class AndroidTtsEngine(
    val context: Context,
    val cacheDir: String = context.externalCacheDir!!.absolutePath + "${File.separator}AndroidTTS",
) {
    companion object {
    }

    private var mTts: TextToSpeech? = null

    private var mEngineName: String = ""

    /**
     * @return true if the engine is initialized successfully
     */
    suspend fun init(engineName: String): Boolean = coroutineScope {
        if (mEngineName != engineName) {
            mEngineName = engineName
            release()
        }

        suspendCancellableCoroutine<Boolean> { continuation ->
            mTts = TextToSpeech(context, { status ->
                when (status) {
                    TextToSpeech.SUCCESS -> {
                        continuation.resume(true)
                    }

                    TextToSpeech.ERROR -> {
                        continuation.resume(false)
                    }
                }
            }, engineName)

            continuation.invokeOnCancellation {
                release()
            }
        }
    }

    val voices: List<Voice>
        get() = mTts?.voices?.toList() ?: emptyList()

    val locales: List<Locale>
        get() = mTts?.availableLanguages?.toList()?.sortedBy { it.toString() } ?: emptyList()

    val voice: Voice?
        get() = mTts?.voice

    fun setVoice(voice: Voice): Boolean {
        return mTts?.setVoice(voice) == TextToSpeech.SUCCESS
    }

    private fun setEnginePlayParams(
        engine: TextToSpeech,
        locale: String,
        voice: String,
        extraParams: List<LocalTtsParameter>?,
        params: AudioParams,
    ): Bundle {
        engine.apply {
            if (locale.isNotEmpty())
                language = Locale.forLanguageTag(locale)

            if (voice.isNotEmpty())
                voices?.forEach {
                    if (it.name == voice) this.voice = it
                }

            setSpeechRate(params.speed)
            setPitch(params.pitch)
            return Bundle().apply {
                if (params.volume != LocalTtsSource.VOLUME_FOLLOW) {
                    putFloat(TextToSpeech.Engine.KEY_PARAM_VOLUME, params.volume)
                }
                extraParams?.forEach { it.putValueFromBundle(this) }
            }
        }
    }

    private val mutex = Mutex()

    /**
     * Generate a wav file for the given text.
     */
    suspend fun getFile(
        text: String,
        locale: String = "",
        voice: String = "",
        extraParams: List<LocalTtsParameter> = emptyList(),
        params: AudioParams = AudioParams(),
    ): Result<File, TtsEngineError> = mutex.withLock {
        val tts = mTts ?: return@withLock Err(TtsEngineError.Initialization)
        coroutineScope {
            val filename = System.currentTimeMillis().toString() + ".wav"
            val file = File(cacheDir, filename)
            if (file.parentFile?.exists() != true && file.parentFile?.mkdirs() != true)
                return@coroutineScope Err(TtsEngineError.File)

            fun delete() {
                try {
                    file.delete()
                } catch (_: Exception) {
                }
            }

            val bundle = setEnginePlayParams(tts, locale, voice, extraParams, params)
            val ret = tts.synthesizeToFile(text, bundle, file, filename)
            if (ret != TextToSpeech.SUCCESS) {
                delete()
                return@coroutineScope Err(TtsEngineError.Engine)
            }

            // 使用 suspendCancellableCoroutine 等待合成完成
            suspendCancellableCoroutine<Result<File, TtsEngineError>> { continuation ->
                tts.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
                    override fun onStart(utteranceId: String?) {
                    }

                    override fun onDone(utteranceId: String?) {
                        continuation.resume(Ok(file))
                    }

                    override fun onError(utteranceId: String?) {
                        // 合成出错，删除文件并返回错误
                        delete()
                        continuation.resume(Err(TtsEngineError.Engine)) // 可以考虑更具体的错误
                    }

                })

                continuation.invokeOnCancellation {
                    delete()
                    mTts?.stop()
                }
            }
        }
    }


    /**
     * Generate a pcm audio stream for the given text.
     */
    suspend fun getStream(
        text: String,
        locale: String = "",
        voice: String = "",
        extraParams: List<LocalTtsParameter> = emptyList(),
        params: AudioParams = AudioParams(),
    ): Result<InputStream, TtsEngineError> = mutex.withLock {
        val tts = mTts ?: return@withLock Err(TtsEngineError.Initialization)

        coroutineScope {
            val filename = System.currentTimeMillis().toString() + ".wav"
            val file = File(cacheDir, filename)
            if (file.parentFile?.exists() != true && file.parentFile?.mkdirs() != true)
                return@coroutineScope Err(TtsEngineError.File)

            fun delete() {
                try {
                    file.delete()
                } catch (_: Exception) {
                }
            }

            val bundle = setEnginePlayParams(tts, locale, voice, extraParams, params)
            val ret = tts.synthesizeToFile(text, bundle, file, filename)
            if (ret != TextToSpeech.SUCCESS)
                return@coroutineScope Err(TtsEngineError.Engine)

            suspendCancellableCoroutine<Result<InputStream, TtsEngineError>> { continuation ->
                val pos = PipedOutputStream()
                val pis = PipedInputStream(pos)
                tts.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
                    override fun onStart(utteranceId: String) {
                        continuation.resume(Ok(pis))
                    }

                    override fun onDone(utteranceId: String) {
                        runCatching {
                            pos.close()
                        }
                        delete()
                    }

                    override fun onError(utteranceId: String) {
                        runCatching {
                            pos.close()
                        }
                        delete()
                    }

                    override fun onAudioAvailable(utteranceId: String, audio: ByteArray) {
                        super.onAudioAvailable(utteranceId, audio)
                        pos.write(audio)
                    }
                })

                continuation.invokeOnCancellation {
                    delete()
                    pos.close()
                    mTts?.stop()
                }
            }
        }

    }

    suspend fun getAudio(
        text: String,
        locale: String = "",
        voice: String = "",
        extraParams: List<LocalTtsParameter> = emptyList(),
        params: AudioParams = AudioParams(),
        listener: Listener,
    ): Result<Unit, TtsEngineError> = mutex.withLock {
        val tts = mTts ?: return@withLock Err(TtsEngineError.Initialization)

        coroutineScope {
            val filename = System.currentTimeMillis().toString() + ".wav"
            val file = File(cacheDir, filename)
            if (file.parentFile?.exists() != true && file.parentFile?.mkdirs() != true)
                return@coroutineScope Err(TtsEngineError.File)

            fun delete() {
                try {
                    file.delete()
                } catch (_: Exception) {
                }
            }

            val bundle = setEnginePlayParams(tts, locale, voice, extraParams, params)
            val ret = tts.synthesizeToFile(text, bundle, file, filename)
            if (ret != TextToSpeech.SUCCESS)
                return@coroutineScope Err(TtsEngineError.Engine)

            suspendCancellableCoroutine<Result<Unit, TtsEngineError>> { continuation ->
                tts.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
                    override fun onStart(utteranceId: String) {
                        listener.start()
                    }

                    override fun onDone(utteranceId: String) {
                        listener.done()
                        continuation.resume(Ok(Unit))
                        delete()
                    }

                    override fun onError(utteranceId: String) {
                        continuation.resume(Err(TtsEngineError.Engine))
                        delete()
                    }

                    override fun onError(utteranceId: String?, errorCode: Int) {
                        super.onError(utteranceId, errorCode)
                    }

                    override fun onAudioAvailable(utteranceId: String, audio: ByteArray) {
                        super.onAudioAvailable(utteranceId, audio)
                        listener.available(audio)
                    }
                })

                continuation.invokeOnCancellation {
                    delete()
                    mTts?.stop()
                }
            }
        }
    }

    suspend fun play(
        text: String,
        locale: String = "",
        voice: String = "",
        extraParams: List<LocalTtsParameter> = emptyList(),
        params: AudioParams = AudioParams(),
        queueMode: Int = TextToSpeech.QUEUE_FLUSH,
    ): Result<Unit, TtsEngineError> = mutex.withLock {
        val tts = mTts ?: return@withLock Err(TtsEngineError.Initialization)

        val bundle = setEnginePlayParams(tts, locale, voice, extraParams, params)
        // The [utteranceId] Cannot be null, it will cause no [OnUtteranceProgressListener]
        val ret = tts.speak(text, queueMode, bundle, "")
        if (ret != TextToSpeech.SUCCESS) return@withLock Err(TtsEngineError.Engine)

        suspendCancellableCoroutine<Result<Unit, TtsEngineError>> { continuation ->
            tts.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
                override fun onStart(utteranceId: String?) {
                }

                override fun onDone(utteranceId: String?) {
                    continuation.resume(Ok(Unit))
                }

                override fun onError(utteranceId: String?) {
                    continuation.resume(Err(TtsEngineError.Engine))
                }
            })

            continuation.invokeOnCancellation {
                tts.stop()
            }
        }
    }

    fun release() {
        mTts?.shutdown()
        mTts = null
    }

    interface Listener {
        fun start()
        fun available(audio: ByteArray)
        fun done()
    }

}